package com.iswust.mianzhoutong.sign.service.impl;

import com.iswust.mianzhoutong.sign.consts.RewardConst;
import com.iswust.mianzhoutong.sign.consts.SignTypeConst;
import com.iswust.mianzhoutong.sign.exception.ApiException;
import com.iswust.mianzhoutong.sign.mapper.SignDetailMapper;
import com.iswust.mianzhoutong.sign.mapper.SignSummaryMapper;
import com.iswust.mianzhoutong.sign.model.SignDetail;
import com.iswust.mianzhoutong.sign.model.SignSummary;
import com.iswust.mianzhoutong.sign.model.dto.*;
import com.iswust.mianzhoutong.sign.service.SignService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;

/**
 * @author liushuai
 * @create 2022-07-14 17:21
 */
@Service
@Slf4j
@Transactional
public class SignServiceImpl implements SignService {

    private final String MONTH_REGEX = "^([1-9]\\d{3})-(([0]?[1-9])|([1][0-2]))$";

    @Autowired
    private SignDetailMapper signDetailMapper;

    @Autowired
    private SignSummaryMapper signSummaryMapper;


    @Override
    public SignSummaryDto getSignMonthSummary(Integer userId) {

        //查询年初到今日的累计签到数
        int signTimesByYears = signSummaryMapper.querySignByYear(userId);

        SignSummaryDto signSummaryDto = signSummaryMapper.querySignMonthData(userId);
        if (signSummaryDto != null) {
            signSummaryDto.setYearSignDays(signTimesByYears);
            return signSummaryDto;
        }

        //如果为空表示是本月第一次签到,增加一条月记录
        SignSummary signSummary = new SignSummary(userId, 0, new Date());
        signSummaryMapper.insertSignSummary(signSummary);

        signSummaryDto = new SignSummaryDto(0, signTimesByYears, 0, new Date());
        return signSummaryDto;
    }

    protected List<SignReward> calcKeepSignRewards(Integer keepDays) {
        List<SignReward> keepRewardList = new ArrayList<>();
        String desc = "奖励" + keepDays + "个成长值";
        SignReward signReward = new SignReward(keepDays, desc, SignReward.RewardObject.GROWTH_VALUE);
        keepRewardList.add(signReward);
        return keepRewardList;
    }

    protected List<SignReward> calcSumSignRewards(Integer sumDays) {
        List<SignReward> keepRewardList = new ArrayList<>();
        //得到对应累积天数的奖励对象
        SignReward signReward = RewardConst.REWARD_MAP.get(sumDays);
        if (signReward == null) {
            return keepRewardList;
        }
        keepRewardList.add(signReward);
        return keepRewardList;
    }

    private List<SignReward> calDateSignRewards(Date date) {
        List<SignReward> keepRewardList = new ArrayList<>();
        return keepRewardList;
    }

    private List<SignReward> calcSignRewards(Integer keepDays, Integer sumDays) {

        List<SignReward> keepSignRewards = this.calcKeepSignRewards(keepDays);
        //根据签到的日期给予奖励
        //TODO 调用calDateSignRewards函数

        List<SignReward> sumSignRewards = this.calcSumSignRewards(sumDays);
        List<SignReward> signRewardsList = new ArrayList<>(keepSignRewards);
        signRewardsList.addAll(sumSignRewards);
        return signRewardsList;
    }

    @Override
    public SignResult doSign(Integer userId) throws ApiException {
        Date date = new Date();

        //判断用户今日是否签到
        Boolean isSign = this.isSign(userId, date);
        if (isSign) {
            throw new ApiException(10001, "该日已签到,请勿重复签到");
        }

        //签到明细表增加一条记录
        SignDetail signDetail =
                new SignDetail(userId, SignTypeConst.SIGN, 0, date);
        int row = signDetailMapper.insertSign(signDetail);
        if (row == 0) {
            throw new ApiException(10002, "签到失败,无法创建今日签到记录");
        }

        //TODO 封装成函数
        //更新签到月统计数据
        SignSummaryDto signSummaryMonth = refreshMonthDate(userId, false);

        //得到本月累计签到天数
        Integer monthSignDays = signSummaryMonth.getMonthSignDays();
        //得到本月连续签到天数
        Integer keepSignDays = this.getKeepSignDays(userId);
        //得到累签奖励和连签奖励
        List<SignReward> signRewards = this.calcSignRewards(keepSignDays, monthSignDays);

        //设置奖励类型并更新到数据库
        //得到最值钱的奖励的类型
        SignReward signReward = signRewards.get(signRewards.size() - 1);
        Integer type = signReward.getRewardObject().getType();
        signDetail.setSignType(type);
        signDetailMapper.updateRewardType(signDetail);

        //封装返回结果
        SignResult signResult = wrapSignResult(signRewards, type, date, signSummaryMonth);
        log.debug("签到返回结果:{}", signResult);
        return signResult;
    }

    /**
     * @param date
     * @param userId
     */
    private void assertSigned(Integer userId, Date date) throws RuntimeException {
        Boolean sign = this.isSign(userId, date);
    }

    @Override
    public SignResult doReSign(Integer userId, Date date) throws ApiException {

        //查询用户该日是否签到
        Boolean isSign = this.isSign(userId, date);
        if (isSign) {
            throw new ApiException(10003, "该日已签到,请勿重复签到");
        }

        //判断用户补签次数
        SignSummaryDto signSummaryDto = this.signSummaryMapper.
                querySignMonthData(userId);
        if (signSummaryDto.getMonthReplenishNumbers() <= 0) {
            throw new ApiException(10004, "补签次数不足,请继续积极签到");
        }

        //签到明细表增加一条记录
        SignDetail signDetail =
                new SignDetail(userId, SignTypeConst.RESIGN, 0, date);
        int row = signDetailMapper.insertSign(signDetail);
        if (row == 0) {
            throw new ApiException(10005, "签到失败,无法创建该日签到记录");
        }

        // TODO 封装函数
        SignSummaryDto signSummaryMonth = refreshMonthDate(userId, true);
        //得到本月累计签到天数
        Integer monthSignDays = signSummaryMonth.getMonthSignDays();
        //得到累签奖励
        List<SignReward> signRewards = this.calcSumSignRewards(monthSignDays);

        //如果有累计签到奖励,则设置奖励类型并更新到数据库
        int type = 0;
        if (!signRewards.isEmpty()) {
            SignReward signReward = signRewards.get(signRewards.size() - 1);
            type = signReward.getRewardObject().getType();
            //更改今日的奖励类型
            signDetail.setSignDate(new Date());
            signDetailMapper.updateRewardType(signDetail);
        }

        //封装返回结果
        SignResult signResult = wrapSignResult(signRewards, type, date, signSummaryMonth);
        log.debug("补签返回结果:{}", signResult);
        return signResult;
    }

    @Override
    public Boolean isSign(Integer userId, Date date) {
        return 1 == signDetailMapper.queryUserIsSign(userId, date);
    }

    @Override
    public List<SignDaysDetailDto> getSignDaysDetails(Integer userId, String yearMonth) throws ApiException {
        boolean matches = Pattern.matches(MONTH_REGEX, yearMonth);
        if (!matches) {
            throw new ApiException(20001, "日期格式错误,格式应为:yyyy-MM");
        }

        //得到该月最大天数
        int monthMaxDays = this.getMonthMaxDays(yearMonth);

        //数据库查询某月的签到记录
        List<SignDaysDetailDto> signDays = signDetailMapper.getSignDateInMonth(userId, yearMonth);

        //封装未签到每一天对应的该月的天数
        List<Integer> signDay = new ArrayList<>();
        //查询已签到的每一天对应的该月的天数
        signDays.forEach(signDaysDetailDto -> {
            signDay.add(signDaysDetailDto.getDay());
        });
        //从1号遍历到本月最大天数
        for (int i = 1; i <= monthMaxDays; i++) {
            //如果该天数不在签到日期内,添加一个该日的未签到对象
            if (!signDay.contains(i)) {
                signDays.add(i - 1, new SignDaysDetailDto(i, SignTypeConst.UNSIGN, 0));
            }
        }

        log.debug("签到详情：{}", signDays);
        return signDays;
    }

    /**
     * 返回连续签到天数
     *
     * @param userId 用户id
     */
    private Integer getKeepSignDays(Integer userId) {
        List<Integer> keepDays = signDetailMapper.queryDiffCmpToday(userId, 6);
        for (int i = 0; i < keepDays.size(); i++) {
            if (!keepDays.get(i).equals(i)) {
                return i;
            }
        }
        return keepDays.size();
    }

    /**
     * @param yearMonth 年月 yyyy-MM
     * @return 该月的最大天数
     */
    private Integer getMonthMaxDays(String yearMonth) {

        //得到整形的年月
        String[] split = yearMonth.split("-");
        int year = Integer.parseInt(split[0]);
        int month = Integer.parseInt(split[1]);
        //API中的 0-11月对应人类的1-12月,需要-1
        month = month - 1;

        //得到该月最大天数
        int monthMax = 0;
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.YEAR, year);
        calendar.set(Calendar.MONTH, month);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        monthMax = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);

        return monthMax;
    }

    /**
     * 更新签到月统计数据
     *
     * @param userId 用户id
     * @return 更新后的月统计数据，详见{@link SignSummaryDto}
     */
    private SignSummaryDto refreshMonthDate(Integer userId, Boolean isReSign) {
        SignSummaryDto signSummaryByMonth = this.getSignMonthSummary(userId);
        //月签到+1
        signSummaryByMonth.setMonthSignDays(signSummaryByMonth.getMonthSignDays() + 1);
        if (isReSign) {
            //补签次数-1
            signSummaryByMonth.setMonthReplenishNumbers(signSummaryByMonth.getMonthReplenishNumbers() - 1);
        }
        //年签到+1
        signSummaryByMonth.setYearSignDays(signSummaryByMonth.getYearSignDays() + 1);
        signSummaryMapper.updateMonthSignData(userId, signSummaryByMonth);
        return signSummaryByMonth;
    }

    /**
     * 封装返回结果
     *
     * @param signRewards      签到奖励
     * @param type             奖励类型
     * @param date             （补）签到时间
     * @param signSummaryMonth 更新后的月汇总信息
     * @return 签到返回结果
     */
    private SignResult wrapSignResult(List<SignReward> signRewards, int type, Date date, SignSummaryDto signSummaryMonth) {
        SignResult signResult = new SignResult();
        //设置签到奖励
        signResult.setRewards(signRewards);
        //设置奖励类型
        signResult.setSignType(type);
        //设置签到日期
        signResult.setDate(date);
        //设置本月统计数据
        signResult.setSignSummaryDto(signSummaryMonth);
        return signResult;
    }

}
